﻿namespace SandScript
{
    using System;
    using System.IO;
    using HtmlAgilityPack;
    using Wrappers;

    public class ContextBuilder : IContextBuilder
    {
        private static readonly string[] Dependencies = new string[] { "state", "tools", "reflector" };

        private readonly IFileSystemWrapper fileSystemWrapper;

        public ContextBuilder(IFileSystemWrapper fileSystemWrapper)
        {
            this.fileSystemWrapper = fileSystemWrapper;
        }

        public string CreateContext(string filePath)
        {
            string tempFolder = this.fileSystemWrapper.GetTemporaryFolder();
            string fileName = Path.GetFileName(filePath);
            string fileFolder = Path.GetDirectoryName(filePath);
            string tempPath = Path.Combine(tempFolder, fileName);
            var text = this.fileSystemWrapper.GetText(filePath);
            var document = new HtmlDocument();

            this.CopyContextFiles(tempFolder);

            document.LoadHtml(text);

            var head = document.DocumentNode.SelectSingleNode("/html/head");

            if (head == null)
            {
                throw new Exception("The provided HTML document does not contain a head element.");
            }

            var scripts = head.SelectNodes("script");

            if (scripts == null || scripts.Count == 0)
            {
                throw new Exception("The provided HTML document does not contain any script references.");
            }

            foreach (var node in scripts)
            {
                this.ProcessScript(node, tempFolder, fileFolder);
            }

            var newLine = document.CreateTextNode(Environment.NewLine);

            foreach (var item in Dependencies)
            {
                var script = document.CreateElement("script");
                script.SetAttributeValue("src", DependencyFile(item));

                if (item == "state")
                {
                    var start = head.SelectSingleNode("script[@data-ss-start]") ?? head.SelectSingleNode("script");

                    if (start != null)
                    {
                        head.InsertBefore(script, start);
                        head.InsertAfter(newLine, script);
                    }
                    else
                    {
                        head.PrependChild(script);
                        head.PrependChild(newLine);
                    }
                }
                else
                {
                    head.AppendChild(script);
                    head.AppendChild(newLine);
                }
            }

            var writer = new StringWriter();

            document.Save(writer);

            this.fileSystemWrapper.Save(tempPath, writer.ToString());

            return tempPath;
        }

        public void DestroyContext(string contextPath)
        {
            var contextFolder = Path.GetDirectoryName(contextPath);

            this.fileSystemWrapper.DeleteDirectory(contextFolder, true);
        }

        private static string DependencyFile(string name)
        {
            return string.Concat("sandScript.", name, ".js");
        }

        private void ProcessScript(HtmlNode node, string tempFolder, string fileFolder)
        {
            var source = node.GetAttributeValue("src", string.Empty);

            if (!string.IsNullOrEmpty(source))
            {
                Uri referenceUri = new Uri(source, UriKind.RelativeOrAbsolute);

                if (!referenceUri.IsAbsoluteUri || referenceUri.IsFile)
                {
                    string path = Path.Combine(fileFolder, source);

                    if (this.fileSystemWrapper.FileExists(path))
                    {
                        var workingPath = this.MakeUniqueIfNeeded(source, tempFolder);
                        this.fileSystemWrapper.CopyFile(path, workingPath);
                        this.fileSystemWrapper.SetFileAttributes(workingPath, FileAttributes.Normal);
                        node.SetAttributeValue("src", Path.GetFileName(workingPath));
                    }
                }
            }
        }

        private void CopyContextFiles(string tempFolder)
        {
            foreach (var item in Dependencies)
            {
                string path;
                string file = DependencyFile(item);

                if (this.fileSystemWrapper.TryResolvePath(Path.Combine("Context", file), out path))
                {
                    var tempPath = Path.Combine(tempFolder, file);
                    this.fileSystemWrapper.CopyFile(path, tempPath);
                    this.fileSystemWrapper.SetFileAttributes(tempPath, FileAttributes.Normal);
                }
            }
        }

        private string MakeUniqueIfNeeded(string fileName, string directoryPath)
        {
            var filePath = Path.Combine(directoryPath, fileName);

            if (this.fileSystemWrapper.FileExists(filePath))
            {
                var randomFileName = this.fileSystemWrapper.GetRandomFileName();
                filePath = Path.Combine(directoryPath, string.Concat(randomFileName, '_', fileName));
            }

            return filePath;
        }
    }
}
